#include "U2GLESHardwareIndexBuffer.h"
#include "U2GLESHardwareBufferManager.h"
#include "U2Exception.h"


U2EG_NAMESPACE_USING


GLESHardwareIndexBuffer::GLESHardwareIndexBuffer(U2HardwareBufferManagerBase* mgr, 
												 IndexType idxType,
                                                 size_t numIndexes,
                                                 U2HardwareBuffer::Usage usage,
                                                 bool useShadowBuffer)
    : U2HardwareIndexBuffer(mgr, idxType, numIndexes, usage, false, useShadowBuffer)
{
	if (idxType == U2HardwareIndexBuffer::IT_32BIT)
	{
		U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
			"32 bit hardware buffers are not allowed in OpenGL ES.",
			"GLESHardwareIndexBuffer");
	}

	if (!useShadowBuffer)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Only support with shadowBuffer",
                    "GLESHardwareIndexBuffer");
    }

    glGenBuffers(1, &mBufferId);
    GL_CHECK_ERROR;

    if (!mBufferId)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
            "Cannot create GL ES index buffer",
            "GLESHardwareIndexBuffer::GLESHardwareIndexBuffer");
    }

    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
    GL_CHECK_ERROR;
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
                 U2GLESHardwareBufferManager::getGLUsage(usage));
    GL_CHECK_ERROR;
}

GLESHardwareIndexBuffer::~GLESHardwareIndexBuffer()
{
    glDeleteBuffers(1, &mBufferId);
    GL_CHECK_ERROR;
}

void GLESHardwareIndexBuffer::unlockImpl(void)
{
    if (mLockedToScratch)
    {
        if (mScratchUploadOnUnlock)
        {
                // have to write the data back to vertex buffer
                writeData(mScratchOffset, mScratchSize, mScratchPtr,
                          mScratchOffset == 0 && mScratchSize == getSizeInBytes());
        }

		// deallocate from scratch buffer
        static_cast<U2GLESHardwareBufferManager*>(
                U2HardwareBufferManager::getSingletonPtr())->deallocateScratch(mScratchPtr);

        mLockedToScratch = false;
    }
    else
    {
#if defined(GL_GLEXT_PROTOTYPES)
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mBufferId );
        
		if(!glUnmapBufferOES( GL_ELEMENT_ARRAY_BUFFER ))
		{
			U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR, 
                        "Buffer data corrupted, please reload", 
                        "GLESHardwareIndexBuffer::unlock");
		}
#else
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Lock to scratch is only supported",
                    "GLESHardwareIndexBuffer::unlockImpl");
#endif
    }
    mIsLocked = false;
}

void* GLESHardwareIndexBuffer::lockImpl(size_t offset,
                                        size_t length,
                                        LockOptions options)
{
    if(mIsLocked)
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Invalid attempt to lock an index buffer that has already been locked",
                    "GLESHardwareIndexBuffer::lock");
    }

    void* retPtr = 0;
    GLenum access = 0;
	U2GLESHardwareBufferManager* glBufManager = static_cast<U2GLESHardwareBufferManager*>(U2HardwareBufferManager::getSingletonPtr());

    if(length < glBufManager->getGLMapBufferThreshold())
    {
        retPtr = glBufManager->allocateScratch((u2uint32)length);
        if (retPtr)
        {
            mLockedToScratch = true;
            mScratchOffset = offset;
            mScratchSize = length;
            mScratchPtr = retPtr;
            mScratchUploadOnUnlock = (options != HBL_READ_ONLY);

            if (options != HBL_DISCARD)
            {
                readData(offset, length, retPtr);
            }
        }
    }
    else
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Invalid Buffer lockSize",
                    "GLESHardwareIndexBuffer::lock");
    }

#if defined(GL_GLEXT_PROTOTYPES)
    if (!retPtr)
	{
		glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, mBufferId );
		// Use glMapBuffer
		if(options == HBL_DISCARD)
		{
			// Discard the buffer
			glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL, 
                            U2GLESHardwareBufferManager::getGLUsage(mUsage));
		}
		if (mUsage & HBU_WRITE_ONLY)
			access = GL_WRITE_ONLY_OES;
        
		void* pBuffer = glMapBufferOES( GL_ELEMENT_ARRAY_BUFFER, access );
        
		if(pBuffer == 0)
		{
			U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR, 
                        "Index Buffer: Out of memory", 
                        "GLESHardwareIndexBuffer::lock");
		}
        
		// return offsetted
		retPtr = static_cast<void*>(static_cast<unsigned char*>(pBuffer) + offset);
        
		mLockedToScratch = false;
	}
	mIsLocked = true;
#endif
    
    return retPtr;
}

void GLESHardwareIndexBuffer::readData(size_t offset,
                                       size_t length,
                                       void* pDest)
{
    if(mUseShadowBuffer)
    {
        // Get data from the shadow buffer
        void* srcData = mpShadowBuffer->lock(offset, length, HBL_READ_ONLY);
        memcpy(pDest, srcData, length);
        mpShadowBuffer->unlock();
    }
    else
    {
        U2_EXCEPT(U2Exception::ERR_INTERNAL_ERROR,
                    "Reading hardware buffer is not supported",
                    "GLESHardwareIndexBuffer::readData");
    }
}

void GLESHardwareIndexBuffer::writeData(size_t offset, size_t length,
                                        const void* pSource,
                                        bool discardWholeBuffer)
{
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
    GL_CHECK_ERROR;

    // Update the shadow buffer
    if (mUseShadowBuffer)
    {
        void* destData = mpShadowBuffer->lock(offset, length,
                                              discardWholeBuffer ? HBL_DISCARD : HBL_NORMAL);
        memcpy(destData, pSource, length);
        mpShadowBuffer->unlock();
    }

    if (offset == 0 && length == mSizeInBytes)
    {
        glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, pSource,
                     U2GLESHardwareBufferManager::getGLUsage(mUsage));
        GL_CHECK_ERROR;
    }
    else
    {
        if (discardWholeBuffer)
        {
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, NULL,
                            U2GLESHardwareBufferManager::getGLUsage(mUsage));
        }

        // Now update the real buffer
        glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, offset, length, pSource);
        GL_CHECK_ERROR;
    }
}

void GLESHardwareIndexBuffer::_updateFromShadow(void)
{
    if (mUseShadowBuffer && mShadowUpdated && !mSuppressHardwareUpdate)
    {
        const void *srcData = mpShadowBuffer->lock(mLockStart, mLockSize,
                                                   HBL_READ_ONLY);

        glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mBufferId);
        GL_CHECK_ERROR;

        // Update whole buffer if possible, otherwise normal
        if (mLockStart == 0 && mLockSize == mSizeInBytes)
        {
            glBufferData(GL_ELEMENT_ARRAY_BUFFER, mSizeInBytes, srcData,
                         U2GLESHardwareBufferManager::getGLUsage(mUsage));
            GL_CHECK_ERROR;
        }
        else
        {
            glBufferSubData(GL_ELEMENT_ARRAY_BUFFER,
                            mLockStart, mLockSize, srcData);
            GL_CHECK_ERROR;
        }

        mpShadowBuffer->unlock();
        mShadowUpdated = false;
    }
}

